﻿//Copyright (c) 2010, Andreas Grünwald
//All rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml.Linq;
using System.Web;
using yaTDL.Extensions;

namespace yaTDL.Core.Toodledo
{

    //for Toodledeo API Documentation see http://www.toodledo.com/info/api_doc.php
    public class ToodledoClientV1
    {

        bool attachAppID = false;   //can no loger be set in settings, as property is not required for ToodledoClientV2 and thus was not implemented in ApplicationSettings which replaced the old settings system

        static string toodledoBaseUrl = "http://api.toodledo.com/api.php?method=";

        public event EventHandler<ErrorEventArgsV1> ErrorOccurred;

        WebClient webClient = new WebClient();


        public int RequestCount { get; set; }

        public DateTime FirstRequest { get; set; }

        public double RequestRatio { get { return RequestCount + 2 / (DateTime.Now - FirstRequest).TotalMinutes; } } //The Ratio is slightly misclaculated intentiaonall, so that there are always 2 request left.



        public ToodledoClientV1()
        {
            FirstRequest = DateTime.Parse("01.01.0001");
        }



        public string GetUserId(string email, string password)
        {
            string url = toodledoBaseUrl + "getUserid;email=" + email +";pass=" + password;

            XElement response = makeRequest(url);

            return response.Value;
        }

        public ToodledoAPIKey GetKey(string userId, string password)
        {           
            string url = toodledoBaseUrl + "getToken;userid=" + userId;
            if (attachAppID) url += ";appid=yaTDL";
                   
            string token = makeRequest(url).Value;
            
            ToodledoAPIKey key = new ToodledoAPIKey();
            key.Key = (password.GetMD5Hash() + token + userId).GetMD5Hash();
            key.Expires = DateTime.Now.AddHours(3.9);            
            
            return key;
        }


        public Collection<Project> GetFolders(string key)
        {         
            Collection<Project> projects = new Collection<Project>();
            
            string url = toodledoBaseUrl + "getFolders;key=" + key;
            
            XElement xElement = makeRequest(url);

            foreach (XElement x in xElement.Elements())
            {
                Project p = new Project();
                p.Name = x.Value;
                p.Id = x.Attribute("id").Value;
                p.Order = Int32.Parse(x.Attribute("order").Value);
                projects.Add(p);
            }

            return projects;
        }

        public bool AddFolder(string key, Project project)
        {
            string url = toodledoBaseUrl + "addFolder;key=" + key + ";title=" + HttpUtility.UrlEncode(project.Name) + ";";

            XElement xElement = makeRequest(url);
            if (xElement.Name == "added")
            {
                project.Id = xElement.Value;
                return true;
            }
            else
            { return false; }

        }

        public bool UpdateFolder(string key, Project project)
        {
            string url = toodledoBaseUrl + "editFolder;key=" + key + ";id=" + project.Id + ";title=" + HttpUtility.UrlEncode(project.Name);
            return requestWithSuccessResponse(url);
        }

        public bool DeleteFolder(string key, string id)
        {
            string url = toodledoBaseUrl + "deleteFolder;key="+ key +";id=" + id +";";
            return requestWithSuccessResponse(url);            
        }

        public ToodledoServerInfo GetServerInfo(string key)
        {
            string url = toodledoBaseUrl + "getServerInfo;key=" + key;

            XElement response = makeRequest(url);

            if (response.Name == "error") { return ToodledoServerInfo.Empty; }

            ToodledoServerInfo serverInfo = new ToodledoServerInfo();
            
            serverInfo.UnixTime = double.Parse(response.Element("unixtime").Value);
            serverInfo.ServerOffset = double.Parse(response.Element("serveroffset").Value);

            //Determine the server's date and time based on it's UnixTime and it's Offset
            serverInfo.Date = (serverInfo.UnixTime + serverInfo.ServerOffset).ToDateTime();
            
            serverInfo.TokenExpires = Decimal.Parse(response.Element("tokenexpires").Value.Replace(".",","));

            return serverInfo;
        }

        public ToodledoAccountInfoV1 GetAccountInfo(string key)
        {
            string url = toodledoBaseUrl + "getAccountInfo;unix=1;key=" + key;

            XElement response = makeRequest(url);

            if (response.Name == "account")
            {
                ToodledoAccountInfoV1 accountInfo = new ToodledoAccountInfoV1();
                accountInfo.UserId = response.Element("userid").Value;
                accountInfo.Alias = response.Element("alias").Value;
                if (response.Element("pro").Value == "1") accountInfo.IsProUser = true;
                accountInfo.DateFormat = Int32.Parse(response.Element("dateformat").Value);
                accountInfo.TimeZone = Int32.Parse(response.Element("timezone").Value);
                accountInfo.HideMonths = Int32.Parse(response.Element("hidemonths").Value);
                accountInfo.HotListPriority = Int32.Parse(response.Element("hotlistpriority").Value);
                accountInfo.HotListDueDate = Int32.Parse(response.Element("hotlistduedate").Value);                
                accountInfo.LastAddEdit = (double.Parse(response.Element("lastaddedit").Value)).ToDateTime();
                accountInfo.LastDelete = (double.Parse(response.Element("lastdelete").Value)).ToDateTime();
                accountInfo.LastFolderEdit = (double.Parse(response.Element("lastfolderedit").Value)).ToDateTime();
                accountInfo.LastContextEdit = (double.Parse(response.Element("lastcontextedit").Value)).ToDateTime();
                accountInfo.LastGoalEdit = (double.Parse(response.Element("lastgoaledit").Value)).ToDateTime();
                               
                return accountInfo;
            }
            else
            { return null; }            
        }

        public Collection<Task> GetTasks(string key, List<Parameter> parameters)
        {
            string url = toodledoBaseUrl + "getTasks;unix=1;key=" + key + ";";

            foreach (Parameter parameter in parameters)
            {
                url += parameter.Name.ToLower() + "=";
                switch (parameter.Name.ToLower())
                {
                    case "title":
                    case "tag":
                    case "folder":
                    case "context":
                    case "goal":
                        url += HttpUtility.UrlEncode((parameter.Value as String));
                        break;

                    case "priority":
                    case "repeat":
                    case "status":
                    case "shorter": 
                    case "longer":
                    case "start":
                    case "end":
                        url += ((int)parameter.Value).ToString();
                        break;

                    case "before":
                    case "after":
                    case "startbefore":
                    case "startafter":
                    case "modbefore":
                    case "modafter":
                    case "compbefore":
                    case "compafter":
                        url += ((DateTime)parameter.Value).ToUnixTime().ToString();
                        break;

                    case "notcomp":
                    case "star":
                        if (parameter.Value.GetType() == typeof(int)) 
                        { 
                            url += ((int)parameter.Value).ToString();
                        }
                        if (parameter.Value.GetType() == typeof(bool)) 
                        {
                            if ((bool)parameter.Value) { url += "1"; }
                            else { url += "0"; }
                        }
                        break;                        
                    
                }
                url += ";";
            }

            if (url.EndsWith(";")){url = url.Remove(url.Length -1);}

            Collection<Task> tasks = new Collection<Task>();
            XElement response = makeRequest(url);

            if (response.Name == "tasks")
            {
                foreach (XElement item in response.Elements("task"))
                {
                    Task t = new Task();
                    t.Id = item.Element("id").Value;
                    t.Content = item.Element("title").Value;
                    t.ProjectId = item.Element("folder").Value;

                    if(String.IsNullOrEmpty(item.Element("modified").Value))
                    {   
                        t.LastEdited = (double.Parse(item.Element("added").Value)).ToDateTime();
                    }
                    else
                    {
                        t.LastEdited = (double.Parse(item.Element("modified").Value)).ToDateTime();
                    }
                    
                    if(String.IsNullOrEmpty(item.Element("duedate").Value))
                    {t.DueDate = new DateTime();}
                    else
                    {
                        t.DueDate = (double.Parse(item.Element("duedate").Value)).ToDateTime().ToLocalTime();
                    }
                    
                    if(String.IsNullOrEmpty(item.Element("completed").Value))
                    {t.Completed = false;}
                    else
                    {t.Completed = true;}

                    t.Recurrence.Repeat = Int32.Parse(item.Element("repeat").Value);

                    if (t.Recurrence.Repeat == 50 || t.Recurrence.Repeat == 150) { t.Recurrence.AdvancedRepeat = item.Element("rep_advanced").Value; }
                    else { t.Recurrence.AdvancedRepeat = ""; }

                    t.Priority= Int32.Parse(item.Element("priority").Value);

                    tasks.Add(t);
                }
            }

            return tasks;
        }


        public bool AddTask(string key, Task t)
        {
            string url = toodledoBaseUrl + "addTask;key=" + key + ";title=" + HttpUtility.UrlEncode(t.Content);
            if (t.DueDate != DateTime.Parse("01.01.0001")) 
            {
                //toodledo seems to only get the right date, if there is a time specified.
                //If you add a Task in the web-interface, it will be set to the time of 06:00:00, so it makes sense to do this, too.
                if (t.DueDate.Hour == 0) { t.DueDate = t.DueDate.AddHours(6); }
                url += ";duedate=" + t.DueDate.ToUnixTime(); 
            }
            if (t.Priority >= -1 && t.Priority <= 3) { url += ";priority=" + t.Priority.ToString(); }
            if (!String.IsNullOrEmpty(t.ProjectId)) { url += ";folder=" + t.ProjectId; }
            if(t.Recurrence.Repeat > 0)
            {
                url += ";repeat=" + t.Recurrence.Repeat.ToString();
                if (t.Recurrence.Repeat == 50 || t.Recurrence.Repeat == 150)
                { url += ";rep_advanced=" + HttpUtility.UrlEncode(t.Recurrence.AdvancedRepeat); }
            }

            if (url.EndsWith(";")) { url = url.Remove(url.Length - 1); }

            XElement response = makeRequest(url);

            if (response.Name == "added")
            {
                t.Id = response.Value;
                return true;
            }
            else
            { return false; }

        }

        public bool UpdateTask(string key, Task t)
        {
            string url = toodledoBaseUrl + "editTask;key=" + key + ";id=" + t.Id + ";title=" + HttpUtility.UrlEncode(t.Content);
            if (!String.IsNullOrEmpty(t.ProjectId)) { url += ";folder=" + t.ProjectId; }
            url += ";completed=";
            if (t.Completed) { url += "1"; }
            else { url += "0"; }

            if (t.DueDate != DateTime.Parse("01.01.0001"))
            {
                //toodledo seems to only get the right date, if there is a time specified.
                //If you add a Task in the web-interface, it will be set to the time of 06:00:00, so it makes sense to do this in here, too.
                if (t.DueDate.Hour == 0) { t.DueDate = t.DueDate.AddHours(6); }
                url += ";duedate=" + t.DueDate.ToUnixTime();
            }

            if (t.Recurrence.Repeat > 0)
            {
                url += ";repeat=" + t.Recurrence.Repeat.ToString();
                if (t.Recurrence.Repeat == 50 || t.Recurrence.Repeat == 150)
                { url += ";rep_advanced=" + t.Recurrence.AdvancedRepeat; }
            }


            if (t.Priority >= -1 && t.Priority <= 3) { url += ";priority=" + t.Priority.ToString(); }

            if (url.EndsWith(";")) { url = url.Remove(url.Length - 1); }

            return requestWithSuccessResponse(url);
        }

        public bool DeleteTask(string key, string id)
        {
            string url = toodledoBaseUrl + "deleteTask;key=" + key + ";id=" + id + ";";
            return requestWithSuccessResponse(url);
        }


        public Collection<string> GetDeletedTasks(string key, DateTime after)
        {
            string url = toodledoBaseUrl + "getDeleted;key=" + key + ";after=" + after.ToUnixTime().ToString();
            XElement response = makeRequest(url);
            
            Collection<string> deletedTasks = new Collection<string>();

            if (response.Name == "deleted")
            {
                foreach (XElement item in response.Elements("task"))
                {
                    deletedTasks.Add(item.Element("id").Value);
                }
            }

            return deletedTasks;

        }

        /// <summary>
        /// Calls the indicated url and returns whether the response was "<success>1</success>" or not.
        /// This method can be used by all Toodledo API methods that are supposed to return such a value;
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private bool requestWithSuccessResponse(string url)
        {
            XElement response = makeRequest(url);
            if (response.Name == "success" && response.Value == "1")
            {return true;}
            else
            { return false; }
        }


        private XElement makeRequest(string url)
        {
            //if (RequestRatio > 2)
            //{ 
            //    OnErrorOccurred("Rate Limit Exceeded");
            //    return XElement.Parse("<error>Rate Limit Exceeded</error>");
            //}

            XElement response = XElement.Parse("<error>Unknown Error</error>");

            string downloadedData = "";

            RequestCount += 1;
            if (FirstRequest == DateTime.Parse("01.01.0001")) { FirstRequest = DateTime.Now; }

            try
            {
                downloadedData = webClient.DownloadString(url);
            }
            catch (Exception)
            {
                OnErrorOccurred("webClient.DownloadString() threw an Exception");          
            }
            try
            {
                response = XElement.Parse(downloadedData);
            }
            catch (Exception)
            {
                OnErrorOccurred("XElement.Parse() threw an Exception");          
            }


            if (response == null)
            {
                OnErrorOccurred("Response is null");
                response = XElement.Parse("<error>Response is null</error>");
            }

            if (response.Name == "error")
            {
                OnErrorOccurred(response.Value);
            }

            return response;

        }

        private void OnErrorOccurred(string errorMessage)
        {
            if (ErrorOccurred != null)
            {
                ErrorOccurred(this, new ErrorEventArgsV1() { Error = errorMessage });
            }
        }
    }

    
}
